package com.github.rpc4j.consumer.asm;

import java.io.FileOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicInteger;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import com.github.rpc4j.common.ServiceRequest;
import com.github.rpc4j.consumer.core.ConsumerExecuteHandler;
import com.github.rpc4j.consumer.core.DefaultExecuteHandler;

//import org.objectweb.asm.attrs.*;
public class ClassDump<T> implements Opcodes {

	private static AtomicInteger CLASS_ID = new AtomicInteger(1);

	public Rpc4jClassLoader loader = new Rpc4jClassLoader();

	public  T  getObject(ConsumerExecuteHandler handler,ServiceRequest request) throws Exception {
		Class clzss = Class.forName(request.getInterfaceName());
		Class implClzss = getImplClass(clzss);
		Constructor constructor = implClzss
				.getConstructor(ConsumerExecuteHandler.class,ServiceRequest.class);
		return (T) constructor.newInstance(handler,request);
	}

	private Class  getImplClass(Class clzss) throws Exception {
		byte[] d = dump(clzss);
        return loader.defineClass(d);
	}
    
	private static byte[] dump(Class clzss) throws Exception {

		String infaceterName = Type.getInternalName(clzss);

		String className = infaceterName + "Impl_" + CLASS_ID.getAndIncrement();

		ClassWriter cw = new ClassWriter(0);
		
		MethodVisitor mv;
		AnnotationVisitor av0;

		cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, className, null,"java/lang/Object", new String[] { infaceterName });

		String simpleName = className.substring(className.lastIndexOf("/")+1);
		cw.visitSource(simpleName + ".java", null);

		createField(cw);
		
		createInitMehtod(cw,className);
		
		int line = 20;
        for (Method m : clzss.getMethods()) {
			createMethod(cw,className,m,line);
		}

		cw.visitEnd();
			
        return cw.toByteArray();
	}
	
    private static void createField(ClassWriter cw){
    	FieldVisitor fv = cw.visitField(ACC_PRIVATE, "handler","Lcom/github/rpc4j/consumer/core/ConsumerExecuteHandler;",null, null);
		fv.visitEnd();
		
		fv = cw.visitField(ACC_PRIVATE, "request", "Lcom/github/rpc4j/common/ServiceRequest;", null, null);
		fv.visitEnd();
			
	}
    
    private static void createInitMehtod(ClassWriter cw,String className){
    	MethodVisitor mv;
    	mv = cw.visitMethod(ACC_PUBLIC,"<init>","(Lcom/github/rpc4j/consumer/core/ConsumerExecuteHandler;Lcom/github/rpc4j/common/ServiceRequest;)V",null, null);
		mv.visitCode();
		
		Label l0 = new Label();
		mv.visitLabel(l0);
		mv.visitLineNumber(9, l0); 
		mv.visitVarInsn(ALOAD, 0);
		mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>","()V");
		
		Label l1 = new Label();
		mv.visitLabel(l1);
		mv.visitLineNumber(10, l1);
		mv.visitVarInsn(ALOAD, 0);
		mv.visitVarInsn(ALOAD, 1);
		mv.visitFieldInsn(PUTFIELD, className, "handler","Lcom/github/rpc4j/consumer/core/ConsumerExecuteHandler;");
		
		Label l2 = new Label();
		mv.visitLabel(l2);
		mv.visitLineNumber(11, l2);
		mv.visitVarInsn(ALOAD, 0);
		mv.visitVarInsn(ALOAD, 2);
		mv.visitFieldInsn(PUTFIELD, className, "request", "Lcom/github/rpc4j/common/ServiceRequest;");
		
		Label l3 = new Label();
		mv.visitLabel(l3);
		mv.visitLineNumber(12, l3);
		mv.visitInsn(RETURN);
		
		Label l4 = new Label();
		mv.visitLabel(l4);
		mv.visitLocalVariable("this", "L" + className + ";", null, l0, l3,0);
		mv.visitLocalVariable("handler", "Lcom/github/rpc4j/consumer/core/ConsumerExecuteHandler;", null, l0, l4, 1);
		mv.visitLocalVariable("request", "Lcom/github/rpc4j/common/ServiceRequest;", null, l0, l4, 2);
		mv.visitMaxs(2, 3);
	}
    
    private static void createMethod(ClassWriter cw,String className,Method m,int line){
    	MethodVisitor mv;
    	Class[] exceptionClasses = m.getExceptionTypes();
    	String[] exceptionStres = null;
    	if(exceptionClasses.length>0){
    		exceptionStres = new String[exceptionClasses.length];
    		int i = 0;
    		for(Class exception:exceptionClasses){
    			exceptionStres[i] = Type.getInternalName(exception);
    			i++;
    		}
    	}
    	
    	mv = cw.visitMethod(ACC_PUBLIC, m.getName(), Type.getMethodDescriptor(m), null, exceptionStres);
		mv.visitCode();
		
		Class[] paramClasses = m.getParameterTypes();
		int paramLength = getParamLength(paramClasses);

		LinkedList<Label> labelList = new LinkedList<Label>();

		int objectParamIndex =0;
		if(paramLength>0){//创建参数对象数组
			Label l0 = new Label();
			mv.visitLabel(l0);
			mv.visitLineNumber(++line, l0);
			if(paramLength<6){
				mv.visitInsn(paramLength+3);
			}else{
				mv.visitIntInsn(BIPUSH, paramLength);
			}
			mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
			objectParamIndex = paramLength+1;
			mv.visitVarInsn(ASTORE, objectParamIndex);
			labelList.addLast(l0);
		}
		
		int argIndex = 0;
		for (Class paramClzss : paramClasses) {//设置每个方法参数到对象数组
			Label l1 = new Label();
			mv.visitLabel(l1);
			mv.visitLineNumber(++line, l1);
			mv.visitVarInsn(ALOAD, objectParamIndex);
			if(argIndex<6){
				mv.visitInsn(argIndex+3);
				mv.visitVarInsn(getLoadOpInsn(paramClzss),argIndex+1);
			}else{
				mv.visitIntInsn(BIPUSH,argIndex);
				mv.visitVarInsn(getLoadOpInsn(paramClzss), argIndex+1);
			}
			objectEncp(paramClzss,mv);
			mv.visitInsn(AASTORE);
			//argIndex++;
			argIndex = paramSetp(paramClzss,argIndex);
			labelList.addLast(l1);
		}
        
		Label l3 = new Label();
		mv.visitLabel(l3);
		mv.visitLineNumber(++line, l3);
		mv.visitVarInsn(ALOAD, 0);
		mv.visitFieldInsn(GETFIELD, className, "request", "Lcom/github/rpc4j/common/ServiceRequest;");
		mv.visitLdcInsn(m.getName());
		mv.visitMethodInsn(INVOKEVIRTUAL, "com/github/rpc4j/common/ServiceRequest", "setMethod", "(Ljava/lang/String;)V");
		labelList.addLast(l3);
		
		Label l4 = new Label();
		mv.visitLabel(l4);
		mv.visitLineNumber(++line, l4);
		mv.visitVarInsn(ALOAD, 0);
		mv.visitFieldInsn(GETFIELD, className, "request", "Lcom/github/rpc4j/common/ServiceRequest;");
		mv.visitLdcInsn(Type.getMethodDescriptor(m));
		mv.visitMethodInsn(INVOKEVIRTUAL, "com/github/rpc4j/common/ServiceRequest", "setMethodDesc", "(Ljava/lang/String;)V");
		labelList.addLast(l4);
		

		Label l5 = new Label();
		mv.visitLabel(l5);
		mv.visitLineNumber(++line, l5);
		mv.visitVarInsn(ALOAD, 0);
		mv.visitFieldInsn(GETFIELD,className,"handler","Lcom/github/rpc4j/consumer/core/ConsumerExecuteHandler;");
		mv.visitVarInsn(ALOAD, 0);
		mv.visitFieldInsn(GETFIELD, className, "request", "Lcom/github/rpc4j/common/ServiceRequest;");
		if(objectParamIndex>0){
			mv.visitVarInsn(ALOAD, objectParamIndex);
		}else{
			mv.visitInsn(ACONST_NULL);
		}
		mv.visitMethodInsn(INVOKEINTERFACE, "com/github/rpc4j/consumer/core/ConsumerExecuteHandler", "execute", "(Lcom/github/rpc4j/common/ServiceRequest;[Ljava/lang/Object;)Ljava/lang/Object;");
		Class retrunClass = m.getReturnType();
         if(!retrunClass.isAssignableFrom(void.class)){
        	getExchengRetrunType(m.getReturnType(),mv);
    		mv.visitInsn(getRetrun(m.getReturnType()));
    	}
   
		labelList.addLast(l5);

		if(retrunClass.isAssignableFrom(void.class)){
		   Label l7 = new Label();
		   mv.visitLabel(l7);
		   mv.visitLineNumber(++line, l7);
		   mv.visitInsn(RETURN);
		   labelList.addLast(l7);
		}
		
		Label l6 = new Label();
		labelList.addLast(l6);
		mv.visitLabel(l6);
        mv.visitLocalVariable("this","L"+className+";",null, labelList.getFirst(), labelList.getLast(), 0);

		argIndex = 0;
		for (Class paramClzss : paramClasses) {
			argIndex = paramSetp(paramClzss,argIndex);
			mv.visitLocalVariable("arg_" +argIndex,Type.getType(paramClzss).getDescriptor(), null,labelList.getFirst(), labelList.getLast(),
					argIndex);
		}
        if(argIndex>0){
        	mv.visitLocalVariable("args", "[Ljava/lang/Object;", null,labelList.get(1), labelList.getLast(), argIndex++);
		}
		mv.visitMaxs(4,++argIndex);
		
		mv.visitEnd();
    }
    
    private static int getLoadOpInsn(Class clzss){
    	if(clzss.isAssignableFrom(boolean.class)||clzss.isAssignableFrom(byte.class)||clzss.isAssignableFrom(char.class)||
    			  clzss.isAssignableFrom(short.class)||clzss.isAssignableFrom(int.class)){
    	   return ILOAD;
    	}else if(clzss.isAssignableFrom(long.class)){
    		return LLOAD;
    	}else if(clzss.isAssignableFrom(float.class)){
    		return FLOAD;
    	}else if(clzss.isAssignableFrom(double.class)){
    		return DLOAD;
    	}else{
    		return ALOAD;
    	}
    }
    
    private static int getParamLength(Class[] paramClasses){
    	int length = 0;
    	for(Class clzss:paramClasses){
    		if(clzss.isAssignableFrom(boolean.class)||clzss.isAssignableFrom(byte.class)||clzss.isAssignableFrom(char.class)||
      			  clzss.isAssignableFrom(short.class)||clzss.isAssignableFrom(int.class)){
      	       length +=1;
      	   }else if(clzss.isAssignableFrom(long.class)){
      		 length +=2;
      	   }else if(clzss.isAssignableFrom(float.class)){
      		 length +=1;
      	   }else if(clzss.isAssignableFrom(double.class)){
      		 length +=2;
      	   }else{
      		 length +=1;
      	   }	
    	}
    	return length;
    	
    }
    
    
    private static void objectEncp(Class clzss,MethodVisitor mv){
    	if(clzss.isAssignableFrom(boolean.class)){
    		mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;");
    	}else if(clzss.isAssignableFrom(byte.class)){
    		mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;");
    	}else if(clzss.isAssignableFrom(char.class)){
    		mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;");
    	}else if(clzss.isAssignableFrom(short.class)){
    		mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;");
    	}else if(clzss.isAssignableFrom(int.class)){
    		mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;");
    	}else if(clzss.isAssignableFrom(long.class)){
  	    	mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;");
  	    }else if(clzss.isAssignableFrom(float.class)){
  	    	mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;");
  	    }else if(clzss.isAssignableFrom(double.class)){
  	    	mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;");
  	    }
    }
    
    private static int paramSetp(Class clzss,int index){
    	if(clzss.isAssignableFrom(boolean.class)){
    		index++;
    	}else if(clzss.isAssignableFrom(byte.class)){
    		index++;
    	}else if(clzss.isAssignableFrom(char.class)){
    		index++;
    	}else if(clzss.isAssignableFrom(short.class)){
    		index++;
    	}else if(clzss.isAssignableFrom(int.class)){
    		index++;
    	}else if(clzss.isAssignableFrom(long.class)){
    		index++;
    		index++;
  	    }else if(clzss.isAssignableFrom(float.class)){
  	    	index++;
  	    }else if(clzss.isAssignableFrom(double.class)){
  	    	index++;
    		index++;
  	    }else{
  	    	index++;
  	    }
    	return index;
    	
    }
    
    private static int getMax(Class clzss,int index){
    	int max = 3;
    	if(clzss.isAssignableFrom(int.class)){
    		index++;
    	}else if(clzss.isAssignableFrom(long.class)){
    		index++;
    		index++;
  	    }else if(clzss.isAssignableFrom(float.class)){
  	    	index++;
  	    }else if(clzss.isAssignableFrom(double.class)){
  	    	index++;
    		index++;
  	    }else{
  	    	index++;
  	    }
    	return index;
    }
    
    private  static int getRetrun(Class clzss){
    	if(clzss.isAssignableFrom(boolean.class)||clzss.isAssignableFrom(byte.class)||clzss.isAssignableFrom(char.class)||
    			  clzss.isAssignableFrom(short.class)||clzss.isAssignableFrom(int.class)){
    		return IRETURN;
    	}else if(clzss.isAssignableFrom(long.class)){
    		return LRETURN;
  	    }else if(clzss.isAssignableFrom(float.class)){
  	    	return FRETURN;
  	    }else if(clzss.isAssignableFrom(double.class)){
  	    	return DRETURN;
  	    }else if(clzss.isAssignableFrom(void.class)){
  	    	return RETURN;
  	    }else{
  	    	return ARETURN;
  	    }
    }
    
    private  static void getExchengRetrunType(Class clzss,MethodVisitor mv){
    	
    	if(clzss.isAssignableFrom(boolean.class)){
    		mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
    		mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z");
    	}else if(clzss.isAssignableFrom(byte.class)){
    		mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
    		mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B");
    	}else if(clzss.isAssignableFrom(char.class)){
    		mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
    		mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C");
    	}else if(clzss.isAssignableFrom(short.class)){
    		mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
  	    	mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S");
    	}else if(clzss.isAssignableFrom(int.class)){
    		mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
    		mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I");
    	}else if(clzss.isAssignableFrom(long.class)){
    		mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
    		mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J");
  	    }else if(clzss.isAssignableFrom(float.class)){
  	    	mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
  	    	mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F");
  	    }else if(clzss.isAssignableFrom(double.class)){
  	    	mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
  	    	mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D");
  	    }else if(clzss.isAssignableFrom(void.class)){
  	    }else{
  	    	mv.visitTypeInsn(CHECKCAST, Type.getInternalName(clzss));
  	    }
    }
    
}
